#region using

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;

#endregion

namespace Pentia.Linq.Mapping
{
  /// <summary>
  /// IoC Container for Sitecore entities.
  /// Used to create classes from Sitecore Templates
  /// </summary>
  public class SitecoreEntityFactory
  {
    private static readonly Dictionary<ID, Type> _registeredTypes = new Dictionary<ID, Type>();

    /// <summary>
    /// Registers a type with a Sitecore template. 
    /// </summary>
    /// <param name="templateInterfaceType">The interface class to deduct the template from. The template must be marked with the SitecoreTemplateAttribute</param>
    /// <param name="instanceType">The type to register to the template</param>
    public void RegisterEntityClass(Type templateInterfaceType, Type instanceType)
    {
      if (templateInterfaceType == null) throw new ArgumentNullException("templateInterfaceType");
      if (instanceType == null) throw new ArgumentNullException("instanceType");

      //Read the template ID from the SitecoreTemplateAttribute attribute on the interface
      object[] attribute = templateInterfaceType.GetCustomAttributes(typeof (SitecoreTemplateAttribute), false);
      if (attribute.Length == 0 || String.IsNullOrEmpty(((SitecoreTemplateAttribute) attribute[0]).ID))
        throw new ConfigurationErrorsException(
          String.Format("{0} must have a SitecoreTemplateAttribute with a TemplateID", templateInterfaceType.FullName));
      ID templateID = ID.Parse(((SitecoreTemplateAttribute) attribute[0]).ID);
      RegisterEntityClass(templateID, instanceType);
    }

    /// <summary>
    /// Registers a type with a Sitecore template ID
    /// </summary>
    /// <param name="templateID"></param>
    /// <param name="instanceType"></param>
    public void RegisterEntityClass(ID templateID, Type instanceType)
    {
      if (templateID.IsNull) throw new ArgumentException("Must be a correct template ID", "templateID");
      if (instanceType == null) throw new ArgumentNullException("instanceType");

      lock (_registeredTypes)
      {
        if (!_registeredTypes.ContainsKey(templateID))
          _registeredTypes.Add(templateID, instanceType);
        else
          _registeredTypes[templateID] = instanceType;
      }
    }

    /// <summary>
    /// Creates the class representing the template which is closest in the inheritance hierarchy to the item
    /// </summary>
    /// <typeparam name="T">The type expected to be returned</typeparam>
    /// <param name="item"></param>
    /// <returns></returns>
    public T Create<T>(Item item)
    {
      if (item == null) throw new ArgumentNullException("item");

      //TODO: find the template depending on inheritance, until then we default to Standard template
      Type returnedType = null;
      if (_registeredTypes.ContainsKey(item.TemplateID))
        returnedType = _registeredTypes[item.TemplateID];
      else
        returnedType = _registeredTypes[TemplateIDs.StandardTemplate];

      Type expectedType = typeof (T);
      ConstructorInfo constructor = returnedType.GetConstructor(new Type[] {typeof (Item)});
      if (constructor == null)
        throw new ConfigurationErrorsException(
          String.Format("The class {0} must have a constructor which takes a single Sitecore.Data.Items.Item argument",
                        returnedType));
      object returnValue = (constructor.Invoke(new object[] {item}));
      if (!expectedType.IsInstanceOfType(returnValue))
        throw new InvalidOperationException(
          String.Format("The item {0} has template ID {1} of type {2} which does not match the expected type of {3}",
                        item.ID, item.TemplateID, returnedType, expectedType));
      return (T) returnValue;
    }
  }
}